/**
 * File name: ReadGraphDriver.java Author: Willard Wang Create date: May 22,
 * 2013
 */
package edu.sjtu.se.prophet.driver;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;

import edu.sjtu.se.prophet.exception.InvalidDataSourceException;
import edu.sjtu.se.prophet.graph.Edge;
import edu.sjtu.se.prophet.graph.Graph;
import edu.sjtu.se.prophet.graph.Node;
import edu.sjtu.se.prophet.io.GraphReader;
import edu.sjtu.se.prophet.io.GraphWriter;
import edu.sjtu.se.prophet.io.IOCommon;
import edu.sjtu.se.prophet.io.MetricsReader;
import edu.sjtu.se.prophet.io.SettableIO;
import edu.sjtu.se.prophet.io.gson.EdgeDeserializer;
import edu.sjtu.se.prophet.io.gson.EdgeSerializer;
import edu.sjtu.se.prophet.io.gson.GraphDeserializer;
import edu.sjtu.se.prophet.io.gson.GraphListDeserializer;
import edu.sjtu.se.prophet.io.gson.GraphListSerializer;
import edu.sjtu.se.prophet.io.gson.GraphSerializer;
import edu.sjtu.se.prophet.io.gson.NodeDeserializer;
import edu.sjtu.se.prophet.io.gson.NodeSerializer;

/**
 * @author deathspeeder
 * 
 */
public class ReadGraphDriver extends DefaultPropertiesLoader {
    private static Logger logger = Logger.getLogger(ReadGraphDriver.class);

    public static String READER_CLASSES = "reader.classes";
    public static String METRICS_CLASSES = "metrics.classes";
    public static String WRITER_CLASSES = "writer.classes";
    public static String WRITE_OBJECT = "object.write";
    public static String READ_OBJECT = "object.read";
    private List<Graph> graphs;

    /**
     * @param args
     * @throws IOException
     * @throws FileNotFoundException
     */
    public static void main(String[] args) throws FileNotFoundException,
            IOException {
        ReadGraphDriver driver = new ReadGraphDriver();
        driver.load(args);

        try {

            if (driver.needReadObject()) {
                driver.readObject();
            } else {
                logger.info("Reading graphs ...");
                driver.readGraph();

                logger.info("Reading metrics ...");
                driver.readMetrics();

                logger.info("Filtering nodes ...");
                driver.filterNodes();
            }

            driver.writeObjectIfNeeded();

            logger.info("Writing graphs ...");
            driver.writeGraph();
        } catch (Exception e) {
            logger.error("Main", e);
        }

    }

    private void writeObjectIfNeeded() throws IOException {
        if (properties.containsKey(WRITE_OBJECT)) {
            logger.info("Writing object ...");
            Gson gson = new GsonBuilder()
                    .registerTypeAdapter((new TypeToken<List<Graph>>() {
                    }).getType(), new GraphListSerializer())
                    .registerTypeAdapter(Graph.class, new GraphSerializer())
                    .registerTypeAdapter(Node.class, new NodeSerializer())
                    .registerTypeAdapter(Edge.class, new EdgeSerializer())
                    .create();
            String json = gson.toJson(graphs);
            BufferedWriter writer = new BufferedWriter(new FileWriter(
                    properties.getProperty(WRITE_OBJECT)));
            writer.write(json);
            writer.close();
        }
    }

    private void readObject() throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(
                properties.getProperty(READ_OBJECT)));
        String json = "";
        try {
            json = reader.readLine();
        } finally {
            reader.close();
        }
        Gson gson = new GsonBuilder()
                .registerTypeAdapter((new TypeToken<List<Graph>>() {
                }).getType(), new GraphListDeserializer())
                .registerTypeAdapter(Graph.class, new GraphDeserializer())
                .registerTypeAdapter(Node.class, new NodeDeserializer())
                .registerTypeAdapter(Edge.class, new EdgeDeserializer())
                .create();
        graphs = gson.fromJson(json, (new TypeToken<List<Graph>>() {
        }).getType());
    }

    private boolean needReadObject() {

        return properties.containsKey(READ_OBJECT);
    }

    // @SuppressWarnings("unchecked")
    // private void readObject() throws FileNotFoundException, IOException,
    // ClassNotFoundException {
    // System.out.println("Reading object ...");
    // @SuppressWarnings("resource")
    // ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
    // properties.getProperty(READ_OBJECT)));
    // graphs = (List<Graph>) ois.readObject();
    // }
    //
    // private void writeObjectIfNeeded() throws FileNotFoundException,
    // IOException {
    // if (properties.containsKey(WRITE_OBJECT)) {
    // System.out.println("Writing object ...");
    // ObjectOutputStream oos = new ObjectOutputStream(
    // new FileOutputStream(properties.getProperty(WRITE_OBJECT)));
    // oos.writeObject(graphs);
    // oos.close();
    // }
    // }

    /**
     * @throws IOException
     * 
     */
    private void filterNodes() throws IOException {
        // TODO check it's OK
        // debug print filter out null metrics nods
        String[] VALID_METRICS = new String[] { "CountInput", "CountLineCode",
                "CountOutput", "CountPath", "Cyclomatic", "Essential",
                "MaxNesting", "RatioCommentToCode" };
        // BufferedWriter br = new BufferedWriter(new FileWriter(new File(
        // "debug.txt")));
        for (Graph g : graphs) {
            // List<Node> uglyNodes = new ArrayList<Node>();
            // br.append(g.getMeta().get("Version").toString() + "\n");
            for (Node node : g.getNodes()) {
                StringBuffer buffer = new StringBuffer();
                buffer.append(node.getId() + ":");
                boolean nullFlag = false;
                for (int i = 0; i < VALID_METRICS.length; i++) {
                    Object obj = node.getMetrics().get(VALID_METRICS[i]);
                    if (obj == null) {
                        nullFlag = true;
                        node.getMetrics().put(VALID_METRICS[i], 0.0);
                        obj = node.getMetrics().get(VALID_METRICS[i]);
                    }
                    buffer.append(obj);
                    buffer.append(",");
                }
                buffer.append(node.getMetrics().get(IOCommon.BUG_COUNT));
                // br.append(buffer.toString() + "\n");
                if (nullFlag) {
                    // uglyNodes.add(node);
                    logger.warn("Null values have set to 0.0 on "
                            + buffer.toString());
                }
            }

            // for (Node node : uglyNodes) {
            // try {
            // g.removeNode(node.getId());
            // } catch (NodeNotFoundException e) {
            // // impossible
            // logger.error("Impossible", e);
            // }
            // }
        }
        // br.close();
    }

    private void setValues(SettableIO target, String className) {
        for (Object obj : properties.keySet()) {
            String key = (String) obj;
            if (key.startsWith(className)) {
                String arg = properties.getProperty(key);
                target.setValue(key.substring(className.length() + 1), arg);
            }
        }
    }

    /**
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws IOException
     * 
     */
    private void writeGraph() throws InstantiationException,
            IllegalAccessException, ClassNotFoundException, IOException {
        if (properties.containsKey(WRITER_CLASSES)) {
            for (String className : properties.getProperty(WRITER_CLASSES)
                    .split(SPLITER)) {
                GraphWriter writer = (GraphWriter) Class.forName(className)
                        .newInstance();
                writer.init(graphs);
                setValues(writer, className);
                writer.wirte();
            }
        }

    }

    /**
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws InvalidDataSourceException
     * @throws IOException
     * 
     */
    private void readMetrics() throws InstantiationException,
            IllegalAccessException, ClassNotFoundException, IOException,
            InvalidDataSourceException {

        if (properties.containsKey(METRICS_CLASSES)) {
            for (String className : properties.getProperty(METRICS_CLASSES)
                    .split(SPLITER)) {
                MetricsReader metricsReader = (MetricsReader) Class.forName(
                        className).newInstance();
                metricsReader.init(graphs);
                setValues(metricsReader, className);
                metricsReader.read();
            }
        }
    }

    /**
     * @throws InvalidDataSourceException
     * @throws IOException
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     * 
     */
    private void readGraph() throws IOException, InvalidDataSourceException,
            InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        graphs = new ArrayList<Graph>();
        if (properties.containsKey(READER_CLASSES)) {
            for (String className : properties.getProperty(READER_CLASSES)
                    .split(SPLITER)) {

                GraphReader reader = (GraphReader) Class.forName(className)
                        .newInstance();
                setValues(reader, className);
                graphs.addAll(reader.read());
            }
        }
    }

    // /**
    // * @param prop
    // */
    // private static void validate(Properties prop) {
    // String[] propNames = new String[] { READER_CLASS, READER_FILES,
    // METRICS_CLASS, METRICS_FILES, CLASSES_DEPTH, CLASSES_COUNT };
    // for (String name : propNames) {
    // if (!prop.containsKey(name)) {
    // System.err.println(name + " not found in properties file.");
    // System.exit(-1);
    // } else {
    // System.out.println(name);
    // }
    // }
    // }
}
